#ifndef Viewport_h
#define Viewport_h

#include <Defines.h>
#include <CSharpPropertyMethods.h>

#include <System/Object.h>
#include <System/IDisposable.h>

#include "../Rectangle.h"
#include "../RMatrix.h"

namespace Riccsson
{
	namespace Xna
	{
		namespace Framework
		{
			namespace Graphics
			{
				struct Viewport
				{
					/// <summary>
					/// Attributes 
					/// </summary>
					public: int X;
					public: int Y;
					public: int Width;
					public: int Height;
					public: float MinDepth;
					public: float MaxDepth;
							
					PROP3(Viewport, float, AspectRatio)
					PROP3(Viewport, Rectangle, Bounds)
					PROP3(Viewport, Rectangle, TitleSafeArea)
					
					COPY_CONSTRUCTOR(Viewport)
					{
						PROP3_INIT_COPY(Viewport, float, AspectRatio);
						PROP3_INIT_COPY(Viewport, Rectangle, Bounds);
						PROP3_INIT_COPY(Viewport, Rectangle, TitleSafeArea);
						
						X = other.X;
						Y = other.Y;
						Width = other.Width;
						Height = other.Height;
						MinDepth = other.MinDepth;
						MaxDepth = other.MaxDepth;
					}

					public: PROP3_GET(float, AspectRatio)
					{
						if ((Height != 0) && (Width != 0))
						{
							return (((float) Width)/((float)Height));
						}
						return 0.0f;
					}
					private: PROP3_SET(float, AspectRatio){throw;}
		
					public: PROP3_GET(Rectangle, Bounds)
					{ 
						Rectangle rectangle;
						rectangle.X = X;
						rectangle.Y = Y;
						rectangle.Width = Width;
						rectangle.Height = Height;
						return rectangle;
					}
					public: PROP3_SET(Rectangle, Bounds)
					{				
						X = value.X;
						Y = value.Y;
						Width = value.Width;
						Height = value.Height;
					}
		
					public: PROP3_GET(Rectangle, TitleSafeArea)
					{
						return Rectangle(X,Y,Width,Height);
					}
					private: PROP3_SET(Rectangle, TitleSafeArea){throw;}
		
					public: Viewport()
						: PROP3_INIT(Viewport, AspectRatio)
						, PROP3_INIT(Viewport, Bounds)
						, PROP3_INIT(Viewport, TitleSafeArea)
					{
						this->X = 0;
						this->Y = 0;
						this->Width = 0;
						this->Height = 0;
						this->MinDepth = 0.0f;
						this->MaxDepth = 0.0f;
					}

					public: Viewport(int X, int Y, int Width, int Height)
						: PROP3_INIT(Viewport, AspectRatio)
						, PROP3_INIT(Viewport, Bounds)
						, PROP3_INIT(Viewport, TitleSafeArea)
					{
						this->X = X;
						this->Y = Y;
						this->Width = Width;
						this->Height = Height;
						this->MinDepth = 0.0f;
						this->MaxDepth = 1.0f;
					}
		
					public: Viewport(Rectangle bounds)
						: PROP3_INIT(Viewport, AspectRatio)
						, PROP3_INIT(Viewport, Bounds)
						, PROP3_INIT(Viewport, TitleSafeArea)
					{
						//: this(bounds.X, bounds.Y, bounds.Width, bounds.Height)
						throw; // TODO: Implement init() method for the 'this()'.
					}

					public: Vector3 Project(Vector3& source, Matrix& projection, Matrix& view, Matrix& world)
					{
						Matrix matrix = Matrix::Multiply(Matrix::Multiply(world, view), projection);
						Vector3 vector = Vector3::Transform(source, matrix);
						float a = (((source.X * matrix.M14) + (source.Y * matrix.M24)) + (source.Z * matrix.M34)) + matrix.M44;
						if (!WithinEpsilon(a, 1.0f))
						{
							vector = (Vector3) (vector / a);
						}
						vector.X = (((vector.X + 1.0f) * 0.5f) * this->Width) + this->X;
						vector.Y = (((-vector.Y + 1.0f) * 0.5f) * this->Height) + this->Y;
						vector.Z = (vector.Z * (this->MaxDepth - this->MinDepth)) + this->MinDepth;
						return vector;
					}

					public: Vector3 Unproject(Vector3& source, Matrix& projection, Matrix& view, Matrix& world)
					{
						Matrix matrix = Matrix::Invert(Matrix::Multiply(Matrix::Multiply(world, view), projection));
						source.X = (((source.X - this->X) / ((float) this->Width)) * 2.0f) - 1.0f;
						source.Y = -((((source.Y - this->Y) / ((float) this->Height)) * 2.0f) - 1.0f);
						source.Z = (source.Z - this->MinDepth) / (this->MaxDepth - this->MinDepth);
						Vector3 vector = Vector3::Transform(source, matrix);
						float a = (((source.X * matrix.M14) + (source.Y * matrix.M24)) + (source.Z * matrix.M34)) + matrix.M44;
						if (!WithinEpsilon(a, 1.0f))
						{
							vector = (Vector3) (vector / a);
						}
						return vector;

					}
		
					private: static bool WithinEpsilon(float a, float b)
					{
						float num = a - b;
						//return ((-1.401298E-45f <= num) && (num <= float.Epsilon));
						throw; // TODO: <= ?????
					}


					public: override GC_PTR<Riccsson::System::string> ToString () const
					{
						//return string.Format ("[Viewport: X={0} Y={1} Width={2} Height={3}]", X,Y, Width,Height);
						throw;
					}
				};
			}
		}
	}
}

#endif
